﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Genuilder.Extensibility;
using Genuilder;
using Genuilder.Extensibility.NRefactory;
using ICSharpCode.NRefactory.Visitors;
using System.Runtime.Serialization;

namespace Genuilder.Extensions
{
	[DataContract]
	public class ProxyGeneratorExtension : IExtension
	{
		class ProxiedInterfaceVisitor : AbstractAstVisitor
		{
			private CodeWriter writer;

			public ProxiedInterfaceVisitor(CodeWriter writer)
			{
				this.writer = writer;
			}

			public override object VisitUsing(ICSharpCode.NRefactory.Ast.Using @using, object data)
			{
				writer.WriteUsing(@using.Name);
				return base.VisitUsing(@using, data);
			}

			public override object VisitNamespaceDeclaration(ICSharpCode.NRefactory.Ast.NamespaceDeclaration namespaceDeclaration, object data)
			{
				using(writer.WriteNamespace(namespaceDeclaration.Name))
				{
					return base.VisitNamespaceDeclaration(namespaceDeclaration, data);
				}
			}


			public override object VisitTypeDeclaration(ICSharpCode.NRefactory.Ast.TypeDeclaration typeDeclaration, object data)
			{
				if(typeDeclaration.Type == ICSharpCode.NRefactory.Ast.ClassType.Interface)
				{
					var typeName = "Proxy" + typeDeclaration.Name;
					writer.Write("public class " + typeName + " : " + typeDeclaration.Name);
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						writer.Write("public " + typeDeclaration.Name + " RealObject { get; private set; }");
						writer.NewLine();
						writer.Write("public " + typeName + "(" + typeDeclaration.Name + " realObject)");
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("this.RealObject = realObject;");
						}
						return base.VisitTypeDeclaration(typeDeclaration, data);
					}

				}
				return null;
			}

			public override object VisitEventDeclaration(ICSharpCode.NRefactory.Ast.EventDeclaration eventDeclaration, object data)
			{
				writer.Write("public virtual event " + eventDeclaration.TypeReference + " " + eventDeclaration.Name);
				writer.NewLine();
				using(writer.WriteBrackets())
				{
					writer.Write("add");
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						writer.Write("RealObject." + eventDeclaration.Name + " += value;");
					}

					writer.Write("remove");
					writer.NewLine();
					using(writer.WriteBrackets())
					{
						writer.Write("RealObject." + eventDeclaration.Name + " -= value;");
					}
				}
				return null;
			}

			public override object VisitMethodDeclaration(ICSharpCode.NRefactory.Ast.MethodDeclaration methodDeclaration, object data)
			{
				writer.Write("public virtual " + methodDeclaration.TypeReference.GetNameOrVoid() + " " + methodDeclaration.Name);
				writer.WriteParameterDeclarations(methodDeclaration.Parameters.Select(p => new ParameterDeclaration(p.TypeReference.ToString(), p.ParameterName)).ToArray());
				writer.NewLine();
				using(writer.WriteBrackets())
				{
					writer.Write((methodDeclaration.TypeReference.IsVoid() ? "" : "return ") + "RealObject." + methodDeclaration.Name);
					writer.WriteParameters(methodDeclaration.Parameters.Select(p => p.ParameterName).ToArray());
					writer.Write(";");
				}
				return null;
			}

			public override object VisitPropertyDeclaration(ICSharpCode.NRefactory.Ast.PropertyDeclaration propertyDeclaration, object data)
			{
				writer.Write("public virtual " + propertyDeclaration.TypeReference + " " + propertyDeclaration.Name);
				writer.NewLine();
				using(writer.WriteBrackets())
				{
					if(propertyDeclaration.HasGetRegion)
					{
						writer.Write("get");
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("return RealObject." + propertyDeclaration.Name + ";");
						}
					}
					if(propertyDeclaration.HasSetRegion)
					{
						writer.Write("set");
						writer.NewLine();
						using(writer.WriteBrackets())
						{
							writer.Write("RealObject." + propertyDeclaration.Name + " = value;");
						}
					}
				}
				return null;
			}
		}
		#region IExtension Members
		[DataMember]
		public string[] Files
		{
			get;
			set;
		}

		[DataMember]
		public FileQuery FileQuery
		{
			get;
			set;
		}

		public void Execute(ExtensionContext extensionContext)
		{
			var fileQuery = FileQuery ?? new FileQuery();
			fileQuery.SelectByNames(Files);
			foreach(var item in extensionContext.GenItems.GetByQuery(fileQuery).Where(i => i.Modified))
			{
				var asyncItem = item.Children.CreateNew("Proxy" + item.File.Name);
				using(var fs = asyncItem.Open())
				{
					CodeWriter writer = new CodeWriter(fs);
					var itemAst = item.GetExtension<CompilationUnitExtension>();
					itemAst.ParseMethodBodies = false;
					itemAst.CompilationUnit.AcceptVisitor(new ProxiedInterfaceVisitor(writer), null);
					writer.Flush();
				}
			}
		}

		#endregion
	}
}
